#!/usr/bin/env/python

import socket
import select
import gobject
import urllib
import gtk
import threading

import user
import chat
import challenge

gobject.threads_init()

class Msn(threading.Thread):

	def __init__(self, email, password, gui):
		"""The main class to handle the dispatch server, or the main msn server. It handles all
			the data from the server, and processes it accordingly."""
		threading.Thread.__init__(self)
		self.socket = socket.socket(socket.AF_INET, 
						socket.SOCK_STREAM) #:Socket to handle the data sent and received
		self.email = email #:the users email given to be used
		self.password = password #: the users password
		self.gui = gui #: An instance of the GUI class, helps control the gui
		self.online_users = {}	#: each user email is a key to its user instance
		self.lst_total = 0	#: count the number of LST the server sends
		self.syn_total = 0	#: save the number of LST to exptect
		self.tr_id = 0 #: the transcation id of the switchboard
		self.msg = 0	#: the initial msg received, if 0 we skip any processing
		self.init_state = 0 #: setting the initial state in the statebox

	def cleanup(self, email, password):
		"""If the login was not successful, we clean up the sockets, and reset
			the variables needed to avoid conflict"""
		self.socket.close()
		self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		self.email = email
		self.password = password
		self.tr_id = 0

	def read_line(self):
		"""Read one line from the server and return it"""
		character = self.socket.recv(1)		
		prev_character = ''
		received_line = '' #: the line received from the server, the end is marked by a '\r\n'
		while character != "\n":
			received_line = received_line + character
			prev_character = character	
			character = self.socket.recv(1)

		if character == "\n" and prev_character == "\r": 
			# check if we get an /r/n which means end of line
			received_line = received_line + character
			return received_line
		

	def parse_sync(self, cmd, params=None):
		"""Send a command to the server, and wait for the response"""
		if params:
			command = cmd + ' ' + str(self.tr_id) + ' ' + params + '\r\n'
		else:
			command = cmd + ' ' + str(self.tr_id) + '\r\n'

		self.socket.send(command)
		print ">> ", repr(command)
		self.tr_id += 1
		server_answer = self.read_line()
		if server_answer:
			print "<< ", repr(server_answer)
			return server_answer

	def run(self):
		"""Start the thread"""
		self.login()


	def login(self):
		"""Login to the MSN server"""
		self.gui.lock_login()
		if self.establish():
			gobject.timeout_add(10, self.watch_socket) #check and read data from the server

	def establish(self):
		"""The authentication phase of the client. Connect to the server and pass the tweener
			authentication method. for more info:
			http://www.hypothetic.org/docs/msn/notification/authentication.php"""
		self.socket.connect(("messenger.hotmail.com", 1863))
		self.parse_sync("VER", "MSNP11 MSNP10")
		self.parse_sync("CVR", 
			"0x0409 linux 7.1 i386 momsn 0.8.1 msmsgs " +self.email+ "")

##	XFR Example : <<< XFR 2 NS *207.46.106.145*:1863 0 207.46.104.20:1863\r\n
		new_server = self.parse_sync("USR", 
			"TWN I " +self.email+ "").split(" ")[3].split(":")[0]

		self.socket.close() #close the main connection to open a new one for the xfr
		self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		self.socket.connect((new_server, 1863))
		self.parse_sync("VER", "MSNP11 MSNP10")
		self.parse_sync("CVR", 
			"0x0409 linux 7.1 i386 momsn 0.8.1 msmsgs " +self.email+ "")


		auth_key = self.parse_sync("USR", 
			"TWN I " +self.email+ "").split(" ")

		if auth_key[0] == "USR":
			try:
				auth_key = challenge.passport_auth(auth_key[4], 
						self.email, self.password)	#retrieve the tweener ticket

				if auth_key != False:
					ok = self.parse_sync("USR", 
					"TWN S " +auth_key+ "").split(" ")[2] #check the reply of the server
					if ok == "OK":
						return True #connection has been established
					else:
						print "Couldnt connect please try again later.."
						return False
				else:
					raise

			except:
				print "Could not login.. to the server!"
				self.gui.unlock_login()
				#Do the relogin here..

		else:
			print "Couldnt connect please try again later.."
			return False

	def watch_socket(self):
		"""Check if the server has send anything to the client, if it has
			process what we got"""
		socket_descriptor = self.socket.fileno()
		read,write,error = select.select([socket_descriptor], [], [], 0)
		if len(read) > 0: #check if there is something to read
			server_data = self.read_line()
			self.process_incoming(server_data)

		return True
		

	def process_incoming(self,data):
		"""Trigger is gobject.IO_IN"""
		data_chunks = data.split(" ")
		command = data_chunks[0]

		if command == "MSG":
			payload = int(data_chunks[3][:-2]) #remove the /r/n at the end
			msg = self.socket.recv(payload)

			if not self.msg: #initial MSG we get from server
				self.syn_total = int(self.parse_sync("SYN", 
										"0 0").split(" ")[4])
				self.msg = 1

			msg = msg.split("\r\n")
			msg_type = msg[1].split(": ")[1].split(" ")[0]
	
			if msg_type == "text/x-msmsgsinitialemailnotification;":
				print "New mail in box"

			else:
				print msg_type
				print msg
			

		elif command == "PRP":
			#PRP used for nicknames and mobile numbers 
			#http://msnpiki.msnfanatic.com/index.php/Command:PRP
			prp_type = data_chunks[1]
			if prp_type == "MFN": #check for display name data
				my_nick =data_chunks[2][:-2]
				self.my_instance = user.Instance(self.email, 
								my_nick,"") #create instance of my details, for later retrieval

				self.gui.contact_window(self.my_instance, self)
				
		elif command == "LST":
			#Sent following a unmatched SYN's response. Contact list
			#http://msnpiki.msnfanatic.com/index.php/Command:LST
			#ex:  <<< LST N=email@addr.ess F=Display%20Name
			self.lst_total += 1
			user_email = data_chunks[1].split("N=")[1] #returns the email
			gtk.gdk.threads_enter() #?
			self.gui.add_user(user_email)
			gtk.gdk.threads_leave() #?
			if self.lst_total == self.syn_total: #make sure we got all the contacts
				self.socket.send("CHG "+str(self.tr_id)+" NLN 0\r\n") #change state to online
				

		elif command == "UBX":
			#UBX is sent by the server to all principles to inform change of personal msg
			# or to inform of media being played
			#ex:  >>> UBX passport@hotmail.com xxx\r\n
			#<Data><PSM>My Personal Message</PSM><CurrentMedia></CurrentMedia></Data>
			payload = int(data_chunks[2][:-2])
			if payload > 0: #check if the user has set a personal msg or media
				ubx = self.socket.recv(payload)
				#+process PM here and song

		elif command == "NLN":
			#Change of presence, i.e change of status or coming online
			#ex: NLN statuscode account_name display_name clientid
			email = data_chunks[2]
			nick = urllib.unquote(data_chunks[3])
			state =data_chunks[1]
			if email not in self.online_users.keys(): #make sure no items repeated
				user_instance = user.Instance(email, nick, state)
				self.online_users[email] = user_instance
				self.gui.add_user(email, user_instance)
			else:	
				#update the state and the nick, (either may be changed)
				self.online_users[email].nick = nick
				self.online_users[email].state = state

		elif command == "ILN":
			#Initial presence notification
			#ex: ILN trid statuscode account_name display_name clientid
			email = data_chunks[3]
			nick = urllib.unquote(data_chunks[4])
			state = data_chunks[2]
			msn_object = ''
			user_instance = user.Instance(email, nick, state)
			if len(data_chunks) == 7: #user has an msn object (dp)
				msn_object = data_chunks[6]
				user_instance.msn_object = msn_object

			self.online_users[email] = user_instance
			self.gui.add_user(email, user_instance)

		elif command == "FLN":
			#Principal signed off 
			#ex: FLN account_name
			email = data_chunks[1][:-2]
			del self.online_users[email]
			self.gui.rem_user(email)


		elif command == "RNG": #?
			#Sent from the notification server whenever invited into a chat
			#ex: RNG sessid address authtype ticket invitepassport invitename\r\n
			#<<< RNG 11752013 207.46.108.38:1863 ... Example%20Name\r\n
			xfr = data_chunks[2].split(":")[0]
			inviter = data_chunks[5]

			#sess_details = [xfr server, auth Key, session ID]
			sess_details = [xfr, data_chunks[4], data_chunks[1]] 

			user_instance = self.online_users[inviter]

			chat_window = self.gui.create_chat_window()
			user_chat = chat.Session(self.my_instance, 
						chat_window, sess_details, user_instance, 1)

			chat_window.add_tab(user_chat.tab, user_instance.email)
			user_chat.invited()
		

		elif command == "CHG":
			my_state = data_chunks[2]
			if not self.init_state:
				self.gui.set_init_state(my_state)
				self.init_state = 1

		elif command == "CHL":
			#unsolicited command used to verify that the client is connected
			#ex: CHL TrID challengestring
			#credit goes to James Bunton <james@delx.cjb.net>
			qry = challenge.doChallenge(data_chunks[2][:-2])
			qry = "QRY "+str(self.tr_id)+" PROD0090YUAUV{2B 32\r\n"+qry
			self.socket.send(qry)

		else:
			#Print the command received, so we can later on process it
			print data_chunks


	def change_nick(self, new_nick):
		"""Send the command to change the users nick"""
		#>>> PRP TrId MFN new_name
		encoded_nick = urllib.quote(new_nick)
		self.parse_sync("PRP", "MFN "+encoded_nick)


	def change_pm(self, new_pm):
		"""Send the command to set a personal message or media"""
		# >>> UUX 10 0\r\n
 		# <Data><PSM>My Personal Message</PSM><CurrentMedia></CurrentMedia></Data>
		payload = "<Data><PSM>"+new_pm+"</PSM><CurrentMedia></CurrentMedia></Data>"
		self.socket.send("UUX "+str(self.tr_id)+" "+str(len(payload))+"\r\n"+payload)

	def change_state(self, new_state):
		"""Send the command to change the users state"""
		#>>> CHG TrID status capabilities objectDescriptor
		#Ex: >>> CHG 12 NLN 0\r\n
		#Ex: <<< CHG 12 NLN 0\r\n
		self.socket.send("CHG "+str(self.tr_id) \
			+" "+new_state+" 0\r\n") #change state to online


	def add_contact(self, user_email):
		"""Add a new contact to the users list"""
		# >>> ADC 50 FL N=name@domain.tld F=name@domain.tld\r\n
		self.parse_sync("ADC", "FL N="+user_email+" F="+user_email)

